home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / pyshared / urlgrabber / progress.py < prev   
Encoding:
Python Source  |  2010-07-08  |  26.4 KB  |  758 lines

  1. #   This library is free software; you can redistribute it and/or
  2. #   modify it under the terms of the GNU Lesser General Public
  3. #   License as published by the Free Software Foundation; either
  4. #   version 2.1 of the License, or (at your option) any later version.
  5. #
  6. #   This library is distributed in the hope that it will be useful,
  7. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  9. #   Lesser General Public License for more details.
  10. #
  11. #   You should have received a copy of the GNU Lesser General Public
  12. #   License along with this library; if not, write to the 
  13. #      Free Software Foundation, Inc., 
  14. #      59 Temple Place, Suite 330, 
  15. #      Boston, MA  02111-1307  USA
  16.  
  17. # This file is part of urlgrabber, a high-level cross-protocol url-grabber
  18. # Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
  19.  
  20.  
  21. import sys
  22. import time
  23. import math
  24. import thread
  25. import fcntl
  26. import struct
  27. import termios
  28.  
  29. # Code from http://mail.python.org/pipermail/python-list/2000-May/033365.html
  30. def terminal_width(fd=1):
  31.     """ Get the real terminal width """
  32.     try:
  33.         buf = 'abcdefgh'
  34.         buf = fcntl.ioctl(fd, termios.TIOCGWINSZ, buf)
  35.         ret = struct.unpack('hhhh', buf)[1]
  36.         if ret == 0:
  37.             return 80
  38.         # Add minimum too?
  39.         return ret
  40.     except: # IOError
  41.         return 80
  42.  
  43. _term_width_val  = None
  44. _term_width_last = None
  45. def terminal_width_cached(fd=1, cache_timeout=1.000):
  46.     """ Get the real terminal width, but cache it for a bit. """
  47.     global _term_width_val
  48.     global _term_width_last
  49.  
  50.     now = time.time()
  51.     if _term_width_val is None or (now - _term_width_last) > cache_timeout:
  52.         _term_width_val  = terminal_width(fd)
  53.         _term_width_last = now
  54.     return _term_width_val
  55.  
  56. class TerminalLine:
  57.     """ Help create dynamic progress bars, uses terminal_width_cached(). """
  58.  
  59.     def __init__(self, min_rest=0, beg_len=None, fd=1, cache_timeout=1.000):
  60.         if beg_len is None:
  61.             beg_len = min_rest
  62.         self._min_len = min_rest
  63.         self._llen    = terminal_width_cached(fd, cache_timeout)
  64.         if self._llen < beg_len:
  65.             self._llen = beg_len
  66.         self._fin = False
  67.  
  68.     def __len__(self):
  69.         """ Usable length for elements. """
  70.         return self._llen - self._min_len
  71.  
  72.     def rest_split(self, fixed, elements=2):
  73.         """ After a fixed length, split the rest of the line length among
  74.             a number of different elements (default=2). """
  75.         if self._llen < fixed:
  76.             return 0
  77.         return (self._llen - fixed) / elements
  78.  
  79.     def add(self, element, full_len=None):
  80.         """ If there is room left in the line, above min_len, add element.
  81.             Note that as soon as one add fails all the rest will fail too. """
  82.  
  83.         if full_len is None:
  84.             full_len = len(element)
  85.         if len(self) < full_len:
  86.             self._fin = True
  87.         if self._fin:
  88.             return ''
  89.  
  90.         self._llen -= len(element)
  91.         return element
  92.  
  93.     def rest(self):
  94.         """ Current rest of line, same as .rest_split(fixed=0, elements=1). """
  95.         return self._llen
  96.  
  97. class BaseMeter:
  98.     def __init__(self):
  99.         self.update_period = 0.3 # seconds
  100.  
  101.         self.filename   = None
  102.         self.url        = None
  103.         self.basename   = None
  104.         self.text       = None
  105.         self.size       = None
  106.         self.start_time = None
  107.         self.last_amount_read = 0
  108.         self.last_update_time = None
  109.         self.re = RateEstimator()
  110.         
  111.     def start(self, filename=None, url=None, basename=None,
  112.               size=None, now=None, text=None):
  113.         self.filename = filename
  114.         self.url      = url
  115.         self.basename = basename
  116.         self.text     = text
  117.  
  118.         #size = None #########  TESTING
  119.         self.size = size
  120.         if not size is None: self.fsize = format_number(size) + 'B'
  121.  
  122.         if now is None: now = time.time()
  123.         self.start_time = now
  124.         self.re.start(size, now)
  125.         self.last_amount_read = 0
  126.         self.last_update_time = now
  127.         self._do_start(now)
  128.         
  129.     def _do_start(self, now=None):
  130.         pass
  131.  
  132.     def update(self, amount_read, now=None):
  133.         # for a real gui, you probably want to override and put a call
  134.         # to your mainloop iteration function here
  135.         if now is None: now = time.time()
  136.         if (now >= self.last_update_time + self.update_period) or \
  137.                not self.last_update_time:
  138.             self.re.update(amount_read, now)
  139.             self.last_amount_read = amount_read
  140.             self.last_update_time = now
  141.             self._do_update(amount_read, now)
  142.  
  143.     def _do_update(self, amount_read, now=None):
  144.         pass
  145.  
  146.     def end(self, amount_read, now=None):
  147.         if now is None: now = time.time()
  148.         self.re.update(amount_read, now)
  149.         self.last_amount_read = amount_read
  150.         self.last_update_time = now
  151.         self._do_end(amount_read, now)
  152.  
  153.     def _do_end(self, amount_read, now=None):
  154.         pass
  155.         
  156. #  This is kind of a hack, but progress is gotten from grabber which doesn't
  157. # know about the total size to download. So we do this so we can get the data
  158. # out of band here. This will be "fixed" one way or anther soon.
  159. _text_meter_total_size = 0
  160. _text_meter_sofar_size = 0
  161. def text_meter_total_size(size, downloaded=0):
  162.     global _text_meter_total_size
  163.     global _text_meter_sofar_size
  164.     _text_meter_total_size = size
  165.     _text_meter_sofar_size = downloaded
  166.  
  167. #
  168. #       update: No size (minimal: 17 chars)
  169. #       -----------------------------------
  170. # <text>                          <rate> | <current size> <elapsed time> 
  171. #  8-48                          1    8  3             6 1            9 5
  172. #
  173. # Order: 1. <text>+<current size> (17)
  174. #        2. +<elapsed time>       (10, total: 27)
  175. #        3. +                     ( 5, total: 32)
  176. #        4. +<rate>               ( 9, total: 41)
  177. #
  178. #       update: Size, Single file
  179. #       -------------------------
  180. # <text>            <pc>  <bar> <rate> | <current size> <eta time> ETA
  181. #  8-25            1 3-4 1 6-16 1   8  3             6 1        9 1  3 1
  182. #
  183. # Order: 1. <text>+<current size> (17)
  184. #        2. +<eta time>           (10, total: 27)
  185. #        3. +ETA                  ( 5, total: 32)
  186. #        4. +<pc>                 ( 4, total: 36)
  187. #        5. +<rate>               ( 9, total: 45)
  188. #        6. +<bar>                ( 7, total: 52)
  189. #
  190. #       update: Size, All files
  191. #       -----------------------
  192. # <text> <total pc> <pc>  <bar> <rate> | <current size> <eta time> ETA
  193. #  8-22 1      5-7 1 3-4 1 6-12 1   8  3             6 1        9 1  3 1
  194. #
  195. # Order: 1. <text>+<current size> (17)
  196. #        2. +<eta time>           (10, total: 27)
  197. #        3. +ETA                  ( 5, total: 32)
  198. #        4. +<total pc>           ( 5, total: 37)
  199. #        4. +<pc>                 ( 4, total: 41)
  200. #        5. +<rate>               ( 9, total: 50)
  201. #        6. +<bar>                ( 7, total: 57)
  202. #
  203. #       end
  204. #       ---
  205. # <text>                                 | <current size> <elapsed time> 
  206. #  8-56                                  3             6 1            9 5
  207. #
  208. # Order: 1. <text>                ( 8)
  209. #        2. +<current size>       ( 9, total: 17)
  210. #        3. +<elapsed time>       (10, total: 27)
  211. #        4. +                     ( 5, total: 32)
  212. #
  213.  
  214. class TextMeter(BaseMeter):
  215.     def __init__(self, fo=sys.stderr):
  216.         BaseMeter.__init__(self)
  217.         self.fo = fo
  218.  
  219.     def _do_update(self, amount_read, now=None):
  220.         etime = self.re.elapsed_time()
  221.         fetime = format_time(etime)
  222.         fread = format_number(amount_read)
  223.         #self.size = None
  224.         if self.text is not None:
  225.             text = self.text
  226.         else:
  227.             text = self.basename
  228.  
  229.         ave_dl = format_number(self.re.average_rate())
  230.         sofar_size = None
  231.         if _text_meter_total_size:
  232.             sofar_size = _text_meter_sofar_size + amount_read
  233.             sofar_pc   = (sofar_size * 100) / _text_meter_total_size
  234.  
  235.         # Include text + ui_rate in minimal
  236.         tl = TerminalLine(8, 8+1+8)
  237.         ui_size = tl.add(' | %5sB' % fread)
  238.         if self.size is None:
  239.             ui_time = tl.add(' %9s' % fetime)
  240.             ui_end  = tl.add(' ' * 5)
  241.             ui_rate = tl.add(' %5sB/s' % ave_dl)
  242.             out = '%-*.*s%s%s%s%s\r' % (tl.rest(), tl.rest(), text,
  243.                                         ui_rate, ui_size, ui_time, ui_end)
  244.         else:
  245.             rtime = self.re.remaining_time()
  246.             frtime = format_time(rtime)
  247.             frac = self.re.fraction_read()
  248.  
  249.             ui_time = tl.add(' %9s' % frtime)
  250.             ui_end  = tl.add(' ETA ')
  251.  
  252.             if sofar_size is None:
  253.                 ui_sofar_pc = ''
  254.             else:
  255.                 ui_sofar_pc = tl.add(' (%i%%)' % sofar_pc,
  256.                                      full_len=len(" (100%)"))
  257.  
  258.             ui_pc   = tl.add(' %2i%%' % (frac*100))
  259.             ui_rate = tl.add(' %5sB/s' % ave_dl)
  260.             # Make text grow a bit before we start growing the bar too
  261.             blen = 4 + tl.rest_split(8 + 8 + 4)
  262.             bar  = '='*int(blen * frac)
  263.             if (blen * frac) - int(blen * frac) >= 0.5:
  264.                 bar += '-'
  265.             ui_bar  = tl.add(' [%-*.*s]' % (blen, blen, bar))
  266.             out = '%-*.*s%s%s%s%s%s%s%s\r' % (tl.rest(), tl.rest(), text,
  267.                                               ui_sofar_pc, ui_pc, ui_bar,
  268.                                               ui_rate, ui_size, ui_time, ui_end)
  269.  
  270.         self.fo.write(out)
  271.         self.fo.flush()
  272.  
  273.     def _do_end(self, amount_read, now=None):
  274.         global _text_meter_total_size
  275.         global _text_meter_sofar_size
  276.  
  277.         total_time = format_time(self.re.elapsed_time())
  278.         total_size = format_number(amount_read)
  279.         if self.text is not None:
  280.             text = self.text
  281.         else:
  282.             text = self.basename
  283.  
  284.         tl = TerminalLine(8)
  285.         ui_size = tl.add(' | %5sB' % total_size)
  286.         ui_time = tl.add(' %9s' % total_time)
  287.         not_done = self.size is not None and amount_read != self.size
  288.         if not_done:
  289.             ui_end  = tl.add(' ... ')
  290.         else:
  291.             ui_end  = tl.add(' ' * 5)
  292.  
  293.         out = '\r%-*.*s%s%s%s\n' % (tl.rest(), tl.rest(), text,
  294.                                     ui_size, ui_time, ui_end)
  295.         self.fo.write(out)
  296.         self.fo.flush()
  297.  
  298.         # Don't add size to the sofar size until we have all of it.
  299.         # If we don't have a size, then just pretend/hope we got all of it.
  300.         if not_done:
  301.             return
  302.  
  303.         if _text_meter_total_size:
  304.             _text_meter_sofar_size += amount_read
  305.         if _text_meter_total_size <= _text_meter_sofar_size:
  306.             _text_meter_total_size = 0
  307.             _text_meter_sofar_size = 0
  308.  
  309. text_progress_meter = TextMeter
  310.  
  311. class MultiFileHelper(BaseMeter):
  312.     def __init__(self, master):
  313.         BaseMeter.__init__(self)
  314.         self.master = master
  315.  
  316.     def _do_start(self, now):
  317.         self.master.start_meter(self, now)
  318.  
  319.     def _do_update(self, amount_read, now):
  320.         # elapsed time since last update
  321.         self.master.update_meter(self, now)
  322.  
  323.     def _do_end(self, amount_read, now):
  324.         self.ftotal_time = format_time(now - self.start_time)
  325.         self.ftotal_size = format_number(self.last_amount_read)
  326.         self.master.end_meter(self, now)
  327.  
  328.     def failure(self, message, now=None):
  329.         self.master.failure_meter(self, message, now)
  330.  
  331.     def message(self, message):
  332.         self.master.message_meter(self, message)
  333.  
  334. class MultiFileMeter:
  335.     helperclass = MultiFileHelper
  336.     def __init__(self):
  337.         self.meters = []
  338.         self.in_progress_meters = []
  339.         self._lock = thread.allocate_lock()
  340.         self.update_period = 0.3 # seconds
  341.         
  342.         self.numfiles         = None
  343.         self.finished_files   = 0
  344.         self.failed_files     = 0
  345.         self.open_files       = 0
  346.         self.total_size       = None
  347.         self.failed_size      = 0
  348.         self.start_time       = None
  349.         self.finished_file_size = 0
  350.         self.last_update_time = None
  351.         self.re = RateEstimator()
  352.  
  353.     def start(self, numfiles=None, total_size=None, now=None):
  354.         if now is None: now = time.time()
  355.         self.numfiles         = numfiles
  356.         self.finished_files   = 0
  357.         self.failed_files     = 0
  358.         self.open_files       = 0
  359.         self.total_size       = total_size
  360.         self.failed_size      = 0
  361.         self.start_time       = now
  362.         self.finished_file_size = 0
  363.         self.last_update_time = now
  364.         self.re.start(total_size, now)
  365.         self._do_start(now)
  366.  
  367.     def _do_start(self, now):
  368.         pass
  369.  
  370.     def end(self, now=None):
  371.         if now is None: now = time.time()
  372.         self._do_end(now)
  373.         
  374.     def _do_end(self, now):
  375.         pass
  376.  
  377.     def lock(self): self._lock.acquire()
  378.     def unlock(self): self._lock.release()
  379.  
  380.     ###########################################################
  381.     # child meter creation and destruction
  382.     def newMeter(self):
  383.         newmeter = self.helperclass(self)
  384.         self.meters.append(newmeter)
  385.         return newmeter
  386.     
  387.     def removeMeter(self, meter):
  388.         self.meters.remove(meter)
  389.         
  390.     ###########################################################
  391.     # child functions - these should only be called by helpers
  392.     def start_meter(self, meter, now):
  393.         if not meter in self.meters:
  394.             raise ValueError('attempt to use orphaned meter')
  395.         self._lock.acquire()
  396.         try:
  397.             if not meter in self.in_progress_meters:
  398.                 self.in_progress_meters.append(meter)
  399.                 self.open_files += 1
  400.         finally:
  401.             self._lock.release()
  402.         self._do_start_meter(meter, now)
  403.         
  404.     def _do_start_meter(self, meter, now):
  405.         pass
  406.         
  407.     def update_meter(self, meter, now):
  408.         if not meter in self.meters:
  409.             raise ValueError('attempt to use orphaned meter')
  410.         if (now >= self.last_update_time + self.update_period) or \
  411.                not self.last_update_time:
  412.             self.re.update(self._amount_read(), now)
  413.             self.last_update_time = now
  414.             self._do_update_meter(meter, now)
  415.  
  416.     def _do_update_meter(self, meter, now):
  417.         pass
  418.  
  419.     def end_meter(self, meter, now):
  420.         if not meter in self.meters:
  421.             raise ValueError('attempt to use orphaned meter')
  422.         self._lock.acquire()
  423.         try:
  424.             try: self.in_progress_meters.remove(meter)
  425.             except ValueError: pass
  426.             self.open_files     -= 1
  427.             self.finished_files += 1
  428.             self.finished_file_size += meter.last_amount_read
  429.         finally:
  430.             self._lock.release()
  431.         self._do_end_meter(meter, now)
  432.  
  433.     def _do_end_meter(self, meter, now):
  434.         pass
  435.  
  436.     def failure_meter(self, meter, message, now):
  437.         if not meter in self.meters:
  438.             raise ValueError('attempt to use orphaned meter')
  439.         self._lock.acquire()
  440.         try:
  441.             try: self.in_progress_meters.remove(meter)
  442.             except ValueError: pass
  443.             self.open_files     -= 1
  444.             self.failed_files   += 1
  445.             if meter.size and self.failed_size is not None:
  446.                 self.failed_size += meter.size
  447.             else:
  448.                 self.failed_size = None
  449.         finally:
  450.             self._lock.release()
  451.         self._do_failure_meter(meter, message, now)
  452.  
  453.     def _do_failure_meter(self, meter, message, now):
  454.         pass
  455.  
  456.     def message_meter(self, meter, message):
  457.         pass
  458.  
  459.     ########################################################
  460.     # internal functions
  461.     def _amount_read(self):
  462.         tot = self.finished_file_size
  463.         for m in self.in_progress_meters:
  464.             tot += m.last_amount_read
  465.         return tot
  466.  
  467.  
  468. class TextMultiFileMeter(MultiFileMeter):
  469.     def __init__(self, fo=sys.stderr):
  470.         self.fo = fo
  471.         MultiFileMeter.__init__(self)
  472.  
  473.     # files: ###/### ###%  data: ######/###### ###%  time: ##:##:##/##:##:##
  474.     def _do_update_meter(self, meter, now):
  475.         self._lock.acquire()
  476.         try:
  477.             format = "files: %3i/%-3i %3i%%   data: %6.6s/%-6.6s %3i%%   " \
  478.                      "time: %8.8s/%8.8s"
  479.             df = self.finished_files
  480.             tf = self.numfiles or 1
  481.             pf = 100 * float(df)/tf + 0.49
  482.             dd = self.re.last_amount_read
  483.             td = self.total_size
  484.             pd = 100 * (self.re.fraction_read() or 0) + 0.49
  485.             dt = self.re.elapsed_time()
  486.             rt = self.re.remaining_time()
  487.             if rt is None: tt = None
  488.             else: tt = dt + rt
  489.  
  490.             fdd = format_number(dd) + 'B'
  491.             ftd = format_number(td) + 'B'
  492.             fdt = format_time(dt, 1)
  493.             ftt = format_time(tt, 1)
  494.             
  495.             out = '%-79.79s' % (format % (df, tf, pf, fdd, ftd, pd, fdt, ftt))
  496.             self.fo.write('\r' + out)
  497.             self.fo.flush()
  498.         finally:
  499.             self._lock.release()
  500.  
  501.     def _do_end_meter(self, meter, now):
  502.         self._lock.acquire()
  503.         try:
  504.             format = "%-30.30s %6.6s    %8.8s    %9.9s"
  505.             fn = meter.basename
  506.             size = meter.last_amount_read
  507.             fsize = format_number(size) + 'B'
  508.             et = meter.re.elapsed_time()
  509.             fet = format_time(et, 1)
  510.             frate = format_number(size / et) + 'B/s'
  511.             
  512.             out = '%-79.79s' % (format % (fn, fsize, fet, frate))
  513.             self.fo.write('\r' + out + '\n')
  514.         finally:
  515.             self._lock.release()
  516.         self._do_update_meter(meter, now)
  517.  
  518.     def _do_failure_meter(self, meter, message, now):
  519.         self._lock.acquire()
  520.         try:
  521.             format = "%-30.30s %6.6s %s"
  522.             fn = meter.basename
  523.             if type(message) in (type(''), type(u'')):
  524.                 message = message.splitlines()
  525.             if not message: message = ['']
  526.             out = '%-79s' % (format % (fn, 'FAILED', message[0] or ''))
  527.             self.fo.write('\r' + out + '\n')
  528.             for m in message[1:]: self.fo.write('  ' + m + '\n')
  529.             self._lock.release()
  530.         finally:
  531.             self._do_update_meter(meter, now)
  532.  
  533.     def message_meter(self, meter, message):
  534.         self._lock.acquire()
  535.         try:
  536.             pass
  537.         finally:
  538.             self._lock.release()
  539.  
  540.     def _do_end(self, now):
  541.         self._do_update_meter(None, now)
  542.         self._lock.acquire()
  543.         try:
  544.             self.fo.write('\n')
  545.             self.fo.flush()
  546.         finally:
  547.             self._lock.release()
  548.         
  549. ######################################################################
  550. # support classes and functions
  551.  
  552. class RateEstimator:
  553.     def __init__(self, timescale=5.0):
  554.         self.timescale = timescale
  555.  
  556.     def start(self, total=None, now=None):
  557.         if now is None: now = time.time()
  558.         self.total = total
  559.         self.start_time = now
  560.         self.last_update_time = now
  561.         self.last_amount_read = 0
  562.         self.ave_rate = None
  563.         
  564.     def update(self, amount_read, now=None):
  565.         if now is None: now = time.time()
  566.         if amount_read == 0:
  567.             # if we just started this file, all bets are off
  568.             self.last_update_time = now
  569.             self.last_amount_read = 0
  570.             self.ave_rate = None
  571.             return
  572.  
  573.         #print 'times', now, self.last_update_time
  574.         time_diff = now         - self.last_update_time
  575.         read_diff = amount_read - self.last_amount_read
  576.         # First update, on reget is the file size
  577.         if self.last_amount_read:
  578.             self.last_update_time = now
  579.             self.ave_rate = self._temporal_rolling_ave(\
  580.                 time_diff, read_diff, self.ave_rate, self.timescale)
  581.         self.last_amount_read = amount_read
  582.         #print 'results', time_diff, read_diff, self.ave_rate
  583.         
  584.     #####################################################################
  585.     # result methods
  586.     def average_rate(self):
  587.         "get the average transfer rate (in bytes/second)"
  588.         return self.ave_rate
  589.  
  590.     def elapsed_time(self):
  591.         "the time between the start of the transfer and the most recent update"
  592.         return self.last_update_time - self.start_time
  593.  
  594.     def remaining_time(self):
  595.         "estimated time remaining"
  596.         if not self.ave_rate or not self.total: return None
  597.         return (self.total - self.last_amount_read) / self.ave_rate
  598.  
  599.     def fraction_read(self):
  600.         """the fraction of the data that has been read
  601.         (can be None for unknown transfer size)"""
  602.         if self.total is None: return None
  603.         elif self.total == 0: return 1.0
  604.         else: return float(self.last_amount_read)/self.total
  605.  
  606.     #########################################################################
  607.     # support methods
  608.     def _temporal_rolling_ave(self, time_diff, read_diff, last_ave, timescale):
  609.         """a temporal rolling average performs smooth averaging even when
  610.         updates come at irregular intervals.  This is performed by scaling
  611.         the "epsilon" according to the time since the last update.
  612.         Specifically, epsilon = time_diff / timescale
  613.  
  614.         As a general rule, the average will take on a completely new value
  615.         after 'timescale' seconds."""
  616.         epsilon = time_diff / timescale
  617.         if epsilon > 1: epsilon = 1.0
  618.         return self._rolling_ave(time_diff, read_diff, last_ave, epsilon)
  619.     
  620.     def _rolling_ave(self, time_diff, read_diff, last_ave, epsilon):
  621.         """perform a "rolling average" iteration
  622.         a rolling average "folds" new data into an existing average with
  623.         some weight, epsilon.  epsilon must be between 0.0 and 1.0 (inclusive)
  624.         a value of 0.0 means only the old value (initial value) counts,
  625.         and a value of 1.0 means only the newest value is considered."""
  626.         
  627.         try:
  628.             recent_rate = read_diff / time_diff
  629.         except ZeroDivisionError:
  630.             recent_rate = None
  631.         if last_ave is None: return recent_rate
  632.         elif recent_rate is None: return last_ave
  633.  
  634.         # at this point, both last_ave and recent_rate are numbers
  635.         return epsilon * recent_rate  +  (1 - epsilon) * last_ave
  636.  
  637.     def _round_remaining_time(self, rt, start_time=15.0):
  638.         """round the remaining time, depending on its size
  639.         If rt is between n*start_time and (n+1)*start_time round downward
  640.         to the nearest multiple of n (for any counting number n).
  641.         If rt < start_time, round down to the nearest 1.
  642.         For example (for start_time = 15.0):
  643.          2.7  -> 2.0
  644.          25.2 -> 25.0
  645.          26.4 -> 26.0
  646.          35.3 -> 34.0
  647.          63.6 -> 60.0
  648.         """
  649.  
  650.         if rt < 0: return 0.0
  651.         shift = int(math.log(rt/start_time)/math.log(2))
  652.         rt = int(rt)
  653.         if shift <= 0: return rt
  654.         return float(int(rt) >> shift << shift)
  655.         
  656.  
  657. def format_time(seconds, use_hours=0):
  658.     if seconds is None or seconds < 0:
  659.         if use_hours: return '--:--:--'
  660.         else:         return '--:--'
  661.     elif seconds == float('inf'):
  662.         return 'Infinite'
  663.     else:
  664.         seconds = int(seconds)
  665.         minutes = seconds / 60
  666.         seconds = seconds % 60
  667.         if use_hours:
  668.             hours = minutes / 60
  669.             minutes = minutes % 60
  670.             return '%02i:%02i:%02i' % (hours, minutes, seconds)
  671.         else:
  672.             return '%02i:%02i' % (minutes, seconds)
  673.             
  674. def format_number(number, SI=0, space=' '):
  675.     """Turn numbers into human-readable metric-like numbers"""
  676.     symbols = ['',  # (none)
  677.                'k', # kilo
  678.                'M', # mega
  679.                'G', # giga
  680.                'T', # tera
  681.                'P', # peta
  682.                'E', # exa
  683.                'Z', # zetta
  684.                'Y'] # yotta
  685.     
  686.     if SI: step = 1000.0
  687.     else: step = 1024.0
  688.  
  689.     thresh = 999
  690.     depth = 0
  691.     max_depth = len(symbols) - 1
  692.     
  693.     # we want numbers between 0 and thresh, but don't exceed the length
  694.     # of our list.  In that event, the formatting will be screwed up,
  695.     # but it'll still show the right number.
  696.     while number > thresh and depth < max_depth:
  697.         depth  = depth + 1
  698.         number = number / step
  699.  
  700.     if type(number) == type(1) or type(number) == type(1L):
  701.         # it's an int or a long, which means it didn't get divided,
  702.         # which means it's already short enough
  703.         format = '%i%s%s'
  704.     elif number < 9.95:
  705.         # must use 9.95 for proper sizing.  For example, 9.99 will be
  706.         # rounded to 10.0 with the .1f format string (which is too long)
  707.         format = '%.1f%s%s'
  708.     else:
  709.         format = '%.0f%s%s'
  710.         
  711.     return(format % (float(number or 0), space, symbols[depth]))
  712.  
  713. def _tst(fn, cur, tot, beg, size, *args):
  714.     tm = TextMeter()
  715.     text = "(%d/%d): %s" % (cur, tot, fn)
  716.     tm.start(fn, "http://www.example.com/path/to/fn/" + fn, fn, size, text=text)
  717.     num = beg
  718.     off = 0
  719.     for (inc, delay) in args:
  720.         off += 1
  721.         while num < ((size * off) / len(args)):
  722.             num += inc
  723.             tm.update(num)
  724.             time.sleep(delay)
  725.     tm.end(size)
  726.  
  727. if __name__ == "__main__":
  728.     # (1/2): subversion-1.4.4-7.x86_64.rpm               2.4 MB /  85 kB/s    00:28     
  729.     # (2/2): mercurial-0.9.5-6.fc8.x86_64.rpm            924 kB / 106 kB/s    00:08     
  730.     if len(sys.argv) >= 2 and sys.argv[1] == 'total':
  731.         text_meter_total_size(1000 + 10000 + 10000 + 1000000 + 1000000 +
  732.                               1000000 + 10000 + 10000 + 10000 + 1000000)
  733.     _tst("sm-1.0.0-1.fc8.i386.rpm", 1, 10, 0, 1000,
  734.          (10, 0.2), (10, 0.1), (100, 0.25))
  735.     _tst("s-1.0.1-1.fc8.i386.rpm", 2, 10, 0, 10000,
  736.          (10, 0.2), (100, 0.1), (100, 0.1), (100, 0.25))
  737.     _tst("m-1.0.1-2.fc8.i386.rpm", 3, 10, 5000, 10000,
  738.          (10, 0.2), (100, 0.1), (100, 0.1), (100, 0.25))
  739.     _tst("large-file-name-Foo-11.8.7-4.5.6.1.fc8.x86_64.rpm", 4, 10, 0, 1000000,
  740.          (1000, 0.2), (1000, 0.1), (10000, 0.1))
  741.     _tst("large-file-name-Foo2-11.8.7-4.5.6.2.fc8.x86_64.rpm", 5, 10,
  742.          500001, 1000000, (1000, 0.2), (1000, 0.1), (10000, 0.1))
  743.     _tst("large-file-name-Foo3-11.8.7-4.5.6.3.fc8.x86_64.rpm", 6, 10,
  744.          750002, 1000000, (1000, 0.2), (1000, 0.1), (10000, 0.1))
  745.     _tst("large-file-name-Foo4-10.8.7-4.5.6.1.fc8.x86_64.rpm", 7, 10, 0, 10000,
  746.          (100, 0.1))
  747.     _tst("large-file-name-Foo5-10.8.7-4.5.6.2.fc8.x86_64.rpm", 8, 10,
  748.          5001, 10000, (100, 0.1))
  749.     _tst("large-file-name-Foo6-10.8.7-4.5.6.3.fc8.x86_64.rpm", 9, 10,
  750.          7502, 10000, (1, 0.1))
  751.     _tst("large-file-name-Foox-9.8.7-4.5.6.1.fc8.x86_64.rpm",  10, 10,
  752.          0, 1000000, (10, 0.5),
  753.          (100000, 0.1), (10000, 0.1), (10000, 0.1), (10000, 0.1),
  754.          (100000, 0.1), (10000, 0.1), (10000, 0.1), (10000, 0.1),
  755.          (100000, 0.1), (10000, 0.1), (10000, 0.1), (10000, 0.1),
  756.          (100000, 0.1), (10000, 0.1), (10000, 0.1), (10000, 0.1),
  757.          (100000, 0.1), (1, 0.1))
  758.